package com.chenjia.proto.converter;

import com.chenjia.proto.converter.annotation.ProtoClass;
import com.google.protobuf.Message;
import org.apache.commons.collections4.CollectionUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 基于 {@link java.lang.invoke.MethodHandle} 实现的转换工具
 */
public final class MethodHandleConverter {
    private MethodHandleConverter() {
    }

    private volatile static MethodHandleConverter instance;

    public static MethodHandleConverter converter() {
        if (instance != null) return instance;
        return getInstance();
    }

    private static synchronized MethodHandleConverter getInstance() {
        if (instance == null) {
            instance = new MethodHandleConverter();
        }
        return instance;
    }

    /**
     * 将 {@link Message} 对象转为 pojo
     */
    @SuppressWarnings("unchecked")
    public <P, M extends Message> P toPojo(final Class<P> pojoClass, final M message) {
        if (message == null) {
            return null;
        }
        P pojo = createPojo(pojoClass);
        // 依次填充
        final List<MethodHandleManager.Message2PojoModel> message2PojoModels = MethodHandleManager.getMessage2PojoModel(message.getClass(), pojoClass);
        message2PojoModels.forEach(model -> {
            try {
                // 对 getter 的返回类型进行判断：集合、POJO复合 等场景下，都需要进一步的递归
                final Method getter = model.messageGetMethod;
                final Method setter = model.pojoSetMethod;
                final Class<?> returnType = MethodHandleManager.getMessageGetterReturnType(getter);
                if (Collection.class.isAssignableFrom(returnType)) {
                    // 1. 对于集合类型，抽取元素类型，再遍历递归处理
                    // 抽取基本类型
                    // pojo-setter: Class   解析参数化类型的第一个
                    final Class elementType = MethodHandleManager.setterParamClassMap.computeIfAbsent(setter, MethodHandleManager::buildSetterCollectionParamClass);
                    final Collection invoke = (Collection) model.messageGetMethodHandle.invoke(message);
                    List<Object> res;
                    if (elementType == null) {
                        res = new ArrayList<>(invoke);
                    } else {
                        res = new ArrayList<>(invoke.size());
                        for (Object o : invoke) {
                            final Object elementPojo = toPojo(elementType, (Message) o);
                            res.add(elementPojo);
                        }
                    }
                    model.pojoSetMethodHandle.invoke(pojo, res);
                } else if (Message.class.isAssignableFrom(returnType)) {
                    // pojo-setter: Class   直接参数的第一个
                    final Class nestedType = MethodHandleManager.setterParamClassMap.computeIfAbsent(setter, MethodHandleManager::buildSetterParamClass);
                    final Object invoke = model.messageGetMethodHandle.invoke(message);
                    model.pojoSetMethodHandle.invoke(pojo, toPojo(nestedType, (Message) invoke));
                } else if (Map.class.isAssignableFrom(returnType)) {
                    // message-getter: <基本类型，可能是 Message>
                    //    pojo-setter: <基本类型，可能是 ProtoClass>
                    // 如果setter的第二个泛型实参是基本类型，则返回 null
                    final Class valueType = MethodHandleManager.setterParamClassMap.computeIfAbsent(setter, MethodHandleManager::buildSetterMapParamClass);
                    if (valueType == null) {
                        // 直接 invoke
                        model.pojoSetMethodHandle.invoke(pojo, model.messageGetMethodHandle.invoke(message));
                    } else {
                        // 处理 value 转换，再 invoke
                        final Map invoke = (Map) model.messageGetMethodHandle.invoke(message);
                        // 遍历处理 value
                        Map res = new HashMap(invoke.size());
                        invoke.forEach((k, v) -> res.put(k, toPojo(valueType, (Message) v)));
                        model.pojoSetMethodHandle.invoke(pojo, res);
                    }
                }
                // Any 支持
//                else if (Any.class.equals(returnType)) {
                // 对 Any 类型进行 unpack 操作，但是 unpack 的对象是
//                    final Any invoke = (Any) getter.invoke(message);
//                    invoke.unpack()
//                }
                else {
                    model.pojoSetMethodHandle.invoke(pojo, model.messageGetMethodHandle.invoke(message));
                }
            } catch (Throwable e) {
                e.printStackTrace();
                // eat, continue
                throw new RuntimeException("出现异常");
            }
        });
        return pojo;
    }

    private <T> T createPojo(final Class<T> clazz) {
        try {
            return clazz.newInstance();
        } catch (InstantiationException e) {
            throw new RuntimeException("Default constructor not found for " + clazz.getSimpleName(), e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Make default constructor of " + clazz.getSimpleName() + " public", e);
        }
    }

    // -------------- toMessage --------------

    /**
     * 将 pojo 对象转为 {@link Message}
     */
    @SuppressWarnings("unchecked")
    public <P, M extends Message> M toMessage(final Class<M> messageClass, final P pojo) {
        if (pojo == null) {
            return null;
        }
        M.Builder builder = createMessageBuilder(messageClass);
        final List<MethodHandleManager.Pojo2messageModel> pojo2messageModels = MethodHandleManager.getPojo2messageModel(pojo.getClass(), builder.getClass());
        pojo2messageModels.forEach(model -> {
            try {
                // TODO 同上，需要区分各种情况，这里的冗余严重，更重要的是没有对这些结构做很好的建模描述
                final Method getter = model.pojoGetMethod;
                final Method setter = model.messageBuilderSetMethod;
                final Class<?> returnType = MethodHandleManager.getPojoGetterReturnType(getter);
                if (Collection.class.isAssignableFrom(returnType)) {
                    // repeated List
                    final Class elementType = MethodHandleManager.setterParamClassMap.computeIfAbsent(setter, MethodHandleManager::buildMessageSetterCollectionParamClass);
                    final Collection invoke = (Collection) model.pojoGetMethodHandle.bindTo(pojo).invokeExact();
                    if (CollectionUtils.isEmpty(invoke)) {
                        return;
                    }
                    List<Object> res;
                    if (elementType == null) {
                        res = new ArrayList<>(invoke);
                    } else {
                        res = new ArrayList<>(invoke.size());
                        for (Object o : invoke) {
                            final Message elementPojo = toMessage(elementType, o);
                            res.add(elementPojo);
                        }
                    }
                    model.messageBuilderSetMethodHandle.bindTo(builder).invokeExact(res);
                } else if (returnType.isAnnotationPresent(ProtoClass.class)) {
                    // 内嵌
                    final Class nestedType = MethodHandleManager.setterParamClassMap.computeIfAbsent(setter, key -> key.getParameterTypes()[0]);
                    final Object invoke = model.pojoGetMethodHandle.bindTo(pojo).invokeExact();
                    if (invoke == null) {
                        return;
                    }
                    model.messageBuilderSetMethodHandle.bindTo(builder).invokeExact(toMessage(nestedType, invoke));
                } else if (Map.class.isAssignableFrom(returnType)) {
                    // Map
                    final Class valueType = MethodHandleManager.setterParamClassMap.computeIfAbsent(setter, MethodHandleManager::buildSetterMapParamClass);
                    if (valueType == null) {
                        // 直接 invoke
                        model.messageBuilderSetMethodHandle.bindTo(builder).invokeExact(model.pojoGetMethodHandle.bindTo(pojo).invokeExact());
                    } else {
                        // 处理 value 转换，再 invoke
                        final Map invoke = (Map) model.pojoGetMethodHandle.bindTo(pojo).invokeExact();
                        // 遍历处理 value
                        if (invoke != null) {
                            Map res = new HashMap(invoke.size());
                            invoke.forEach((k, v) -> res.put(k, toMessage(valueType, v)));
                            model.messageBuilderSetMethodHandle.bindTo(builder).invokeExact(res);
                        }
                    }
                } else {
                    final Object invoke = model.pojoGetMethodHandle.bindTo(pojo).invokeExact();
                    model.messageBuilderSetMethodHandle.bindTo(builder).invokeExact(invoke);
                }
            } catch (Throwable e) {
                e.printStackTrace();
            }
        });
        return (M) builder.build();
    }

    private <M extends Message> M.Builder createMessageBuilder(final Class<M> messageClass) {
        try {
            return (M.Builder) messageClass.getDeclaredMethod("newBuilder").invoke(null);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Can't access 'newBuilder()' method for " + messageClass.getName(), e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException("Can't instantiate protobuf builder for " + messageClass.getName(), e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("Method 'newBuilder()' not found in " + messageClass.getName(), e);
        }
    }
}
